package com.tantou.java.structandalgorithm.search;

import java.util.Arrays;

/**
 * 二分查找
 * 针对的是有序集合
 * <p>
 * 二分查找虽然性能比较优秀，但应用场景也比较有限。底层必须依赖数组，并且还要求数据是有序的。对于较小规模的数据查找，我们直接使用顺序遍历就可以了，二分查找的优势并不明显。二分查找更适合处理静态数据，也就是没有频繁的数据插入、删除操作。
 */
public class OtherBinarySearch {

    public static void main(String[] args) {
        int[] arr = {1, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
        int idx = searchFirstMatch(arr, 9);
        System.out.println(idx);
    }

    //有序数组中存在重复元素，要求是找到第一个满足的元素
    public static int searchFirstMatch(int[] nums, int target) {
        Arrays.sort(nums);

        int low = 0;
        int high = nums.length - 1;
        while (low <= high) {
            int mid = (low + high) / 2;
            if (nums[mid] > target) {
                //如果中间数大于目标值，说明在左边
                high = mid - 1;
            } else if (nums[mid] < target) {
                low = mid + 1;
            } else {
                //如果nums[mid]==target，需要判断前一个指不等于target，即跟前一个不是相同元素
                //如果mid==0，即第一个元素
                if (mid == 0 || nums[mid - 1] != target) {
                    return mid;
                } else {
                    /**
                     * 如果nums[mid-1]==target,即前一个元素等于target，说明nums[mid]不是我们要查找的第一个元素，
                     * 继续往[low,mid-1]查找
                     */
                    high = mid - 1;
                }
            }
        }
        return -1;
    }

    //有序数组中存在重复元素，要求是找到最后一个满足的元素
    public static int searchLastMatch(int[] nums, int target) {
        Arrays.sort(nums);

        int low = 0;
        int high = nums.length - 1;
        while (low <= high) {
            int mid = (low + high) / 2;
            if (nums[mid] > target) {
                //如果中间数大于目标值，说明在左边
                high = mid - 1;
            } else if (nums[mid] < target) {
                low = mid + 1;
            } else {
                //如果nums[mid]==target，需要判断后一个指不等于target，即跟后一个不是相同元素
                //如果mid==0，即第一个元素
                if (mid == 0 || nums[mid + 1] != target) {
                    return mid;
                } else {
                    /**
                     * 如果nums[mid+1]==target,即后一个元素等于target，说明nums[mid]不是我们要查找的第一个元素，
                     * 继续往[mid+1,high]查找
                     */
                    low = mid + 1;
                }
            }
        }
        return -1;
    }

    //有序数组中存在重复元素，要求是找到第一个大于等于给定值的元素
    public static int searchFirstGte(int[] nums, int target) {
        Arrays.sort(nums);

        int low = 0;
        int high = nums.length - 1;
        while (low <= high) {
            int mid = (low + high) / 2;
            if (nums[mid] >= target) {
                //如果中间元素大于等于目标值，
                //判断前一个元素是否小于target,如果小于target且中间元素又大于等于目标值
                if (mid == 0 || nums[mid - 1] < target) {
                    return mid;
                } else {
                    high = mid - 1;
                }
            } else {
                low = mid + 1;
            }
        }
        return -1;
    }

    //有序数组中存在重复元素，要求是找到最后大于等于给定值的元素
    public static int searchLastGte(int[] nums, int target) {
        Arrays.sort(nums);

        int low = 0;
        int high = nums.length - 1;
        int n = nums.length;
        while (low <= high) {
            int mid = (low + high) / 2;
            if (nums[mid] > target) {
                high = mid - 1;
            } else {
                //中间元素大于目标值，且中间元素的后一个元素大于目标值
                if (mid == n - 1 || nums[mid + 1] > target) {
                    return mid;
                } else {
                    low = mid + 1;
                }
            }
        }
        return -1;
    }

}
